/*
 *
 *  Connection Manager
 *
 *  Copyright (C) 2007-2009  Intel Corporation. All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <net/route.h>
#include <net/ethernet.h>
#include <linux/if_arp.h>
#include <linux/if_addr.h>
#include <linux/wireless.h>
#include <linux/rtnetlink.h>

#include "connman.h"

#define	_DBG_INET(fmt, arg...)	DBG(DBG_INET, fmt, ## arg)

/*
 * Create a socket suitable for network ioctls.
 *
 * Return <0 and log a msg on failure.
 *
 * TODO(sleffler) keep a socket open instead of closing after each use?
 */
static int getsocket(const char *func, unsigned char family)
{
	int sk = socket(family, SOCK_DGRAM, 0);
	if (sk < 0)
		connman_error("%s: socket: %s", func, strerror(errno));
	return sk;
}

/*
 * Get an interface's name; it's returned in the struct ifreq passed
 * in which is made usable for subsequent ioctls.
 *
 * Return -1 and log a msg on failure.
 */
static int getifname(int sk, int index, struct ifreq *ifr, const char *func)
{
	memset(ifr, 0, sizeof(*ifr));
	ifr->ifr_ifindex = index;
	if (ioctl(sk, SIOCGIFNAME, ifr) < 0) {
		connman_error("%s: SIOCGIFNAME(index %d): %s", func,
		    ifr->ifr_ifindex, strerror(errno));
		return -1;
	}
	return 0;
}

static void iferror(const char *func, const char *op, const char *name)
{
	connman_error("%s: %s(%s): %s", func, op, name, strerror(errno));
}

/*
 * Return an interface's index given its name.
 *
 * Return -1 and log a msg on failure.
 */
int connman_inet_ifindex(const char *name)
{
	struct ifreq ifr;
	int sk, err;

	if (name == NULL)
		return -1;	/* XXX log? */

	sk = getsocket(__func__, PF_INET);
	if (sk < 0)
		return -1;

	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
	err = ioctl(sk, SIOCGIFINDEX, &ifr);
	if (err < 0)
		iferror(__func__, "SIOCGIFINDEX", name);
	close(sk);

	return (err < 0 ? -1 : ifr.ifr_ifindex);
}

/*
 * Return the interface's name given it's index.  The caller is
 * responsible for the storage used to hold the name.
 *
 * Return NULL and log a msg on failure.
 */
char *connman_inet_ifname(int index)
{
	struct ifreq ifr;
	int sk, err;

	if (index < 0)
		return NULL;	/* XXX log? */

	sk = getsocket(__func__, PF_INET);
	if (sk < 0)
		return NULL;

	err = getifname(sk, index, &ifr, __func__);
	close(sk);

	return (err < 0 ? NULL : g_strdup(ifr.ifr_name));
}

/*
 * Adjust flags on an interface.  If flags is >= 0 then
 * they are "added", otherwise they are "subtracted".
 * Return 0 on success. If we are adjusting IFF_UP and
 * the interace is already up/down return -EALREADY.
 *
 * Return <0 (-errno) on failure and log a msg.
 */
static int inet_setflags(int index, int flags, const char *func)
{
	struct ifreq ifr;
	int sk, err;

	sk = getsocket(func, PF_INET);
	if (sk < 0)
		return -errno;

	err = 0;
	if (getifname(sk, index, &ifr, func) < 0) {
		err = -errno;
		goto done;
	}
	if (ioctl(sk, SIOCGIFFLAGS, &ifr) < 0) {
		iferror(func, "SIOCGIFFLAGS", ifr.ifr_name);
		err = -errno;
		goto done;
	}
	if (flags < 0) {
		if ((ifr.ifr_flags & IFF_UP) == 0) {
			err = -EALREADY;
			goto done;
		}
		ifr.ifr_flags &= ~(-flags);
	} else {
		if (ifr.ifr_flags & IFF_UP) {
			err = -EALREADY;
			goto done;
		}
		ifr.ifr_flags |= flags;
	}
	if (ioctl(sk, SIOCSIFFLAGS, &ifr) < 0) {
		iferror(func, "SIOCSIFFLAGS", ifr.ifr_name);
		err = -errno;
	}
done:
	close(sk);
	return err;
}

/*
 * Mark the specified interface UP.  Return 0 on success.
 * If the interace is already up return -EALREADY.
 *
 * Return <0 (-errno) on failure and log a msg.
 */
int connman_inet_ifup(int index)
{
	return inet_setflags(index, IFF_UP, __func__);
}

/*
 * Mark the specified interface !UP.  Return 0 on success.
 * If the interace is already down return -EALREADY.
 *
 * Return <0 (-errno) on failure and log a msg.
 */
int connman_inet_ifdown(int index)
{
	return inet_setflags(index, -IFF_UP, __func__);
}

/*
 * Get an interface's h/w address and leave it in the struct ifreq
 * passed in.  The interface is specified by its index.
 *
 * Return -1 and log a msg on failure.
 */
static int getifhwaddr(int index, struct ifreq *ifr, const char *func)
{
	int sk, err;

	if (index < 0)
		return -1;

	sk = getsocket(func, PF_INET);
	if (sk < 0)
		return -1;

	err = getifname(sk, index, ifr, func);
	if (err < 0)
		goto done;

	err = ioctl(sk, SIOCGIFHWADDR, ifr);
	if (err < 0)
		iferror(__func__, "SIOCGIFHWADDR", ifr->ifr_name);
done:
	close(sk);
	return err;
}

/*
 * Return an interface's protocol type/family.  In the event
 * of an error we return ARPHRD_VOID and log a msg.
 */
static unsigned short index2type(int index)
{
	struct ifreq ifr;
	int err;

	err = getifhwaddr(index, &ifr, __func__);
	return (err < 0 ? ARPHRD_VOID : ifr.ifr_hwaddr.sa_family);
}

/*
 * Construct an interface's identifier from the hardware address
 * and any specified prefix string.  The caller is responsible for
 * reclaiming storage associated with the returned string.
 *
 * Return NULL and log a msg on failure.
 */
static char *index2ident(int index, const char *prefix)
{
	struct ifreq ifr;
	struct ether_addr eth;
	char *str;

	if (getifhwaddr(index, &ifr, __func__) < 0)
		return NULL;

	memcpy(&eth, &ifr.ifr_hwaddr.sa_data, sizeof(eth));
	str = g_strdup_printf("%s%02x%02x%02x%02x%02x%02x",
	    prefix ? prefix : "",
	    eth.ether_addr_octet[0], eth.ether_addr_octet[1],
	    eth.ether_addr_octet[2], eth.ether_addr_octet[3],
	    eth.ether_addr_octet[4], eth.ether_addr_octet[5]);
	if (str == NULL)
		connman_error("%s: no memory", __func__);
	return str;
}

/*
 * Return an interface's hardware address.  The caller is
 * responsible for reclaiming storage associated with the
 * returned string.
 *
 * Return NULL and log a msg on failure.
 *
 * NB: This should just be index2ident(index, NULL) except each
 *     nibble is formatted w/ %02X instead of %02x.
 */
static char *index2addr(int index)
{
	struct ifreq ifr;
	struct ether_addr eth;
	char *str;

	if (getifhwaddr(index, &ifr, __func__) < 0)
		return NULL;

	memcpy(&eth, &ifr.ifr_hwaddr.sa_data, sizeof(eth));
	str = g_strdup_printf("%02X%02X%02X%02X%02X%02X",
	    eth.ether_addr_octet[0], eth.ether_addr_octet[1],
	    eth.ether_addr_octet[2], eth.ether_addr_octet[3],
	    eth.ether_addr_octet[4], eth.ether_addr_octet[5]);
	if (str == NULL)
		connman_error("%s: no memory", __func__);
	return str;
}

static connman_bool_t _hasclassnetdir(const char *type, const char *devname)
{
	char pathname[PATH_MAX];
	struct stat st;

	snprintf(pathname, PATH_MAX, "/sys/class/net/%s/%s", devname, type);
	return (stat(pathname, &st) == 0 && (st.st_mode & S_IFDIR) ?
	    TRUE : FALSE);
}

static connman_bool_t isphy80211(const char *devname)
{
	return _hasclassnetdir("phy80211", devname);
}

/*
 * Return an indication of whether the interface is using
 * the mac80211 support in the kernel.
 *
 * Return FALSE and log a msg if unable to find an answer.
 */
connman_bool_t connman_inet_is_mac80211(int index)
{
	connman_bool_t result = FALSE;
	struct ifreq ifr;
	int sk;

	sk = getsocket(__func__, PF_INET);
	if (sk < 0)
		return FALSE;

	if (getifname(sk, index, &ifr, __func__) == 0)
		result = isphy80211(ifr.ifr_name);
	close(sk);
	return result;
}

static connman_bool_t isbonding(const char *devname)
{
	return _hasclassnetdir("bonding", devname);
}

static connman_bool_t isbridge(const char *devname)
{
	return _hasclassnetdir("bridge", devname);
}

static connman_bool_t iswimax(const char *devname)
{
	return _hasclassnetdir("wimax", devname);
}

static connman_bool_t hasiwname(const char *devname)
{
	struct iwreq iwr;
	int sk, err;

	sk = getsocket(__func__, PF_INET);
	if (sk < 0)
		return FALSE;

	memset(&iwr, 0, sizeof(iwr));
	strncpy(iwr.ifr_ifrn.ifrn_name, devname, IFNAMSIZ);
	err = ioctl(sk, SIOCGIWNAME, &iwr);
	close(sk);
	return (err == 0 ? TRUE : FALSE);
}

/*
 * Return the internal device type using various heuristics.
 */
enum connman_device_type __connman_inet_get_device_type(int index)
{
	enum connman_device_type devtype;
	char *devname;

	devname = connman_inet_ifname(index);
	if (devname == NULL)
		return CONNMAN_DEVICE_TYPE_UNKNOWN;

	devtype = CONNMAN_DEVICE_TYPE_UNKNOWN;
	switch (index2type(index)) {
	case ARPHRD_ETHER:
		if (isphy80211(devname) == TRUE || hasiwname(devname) == TRUE)
			devtype = CONNMAN_DEVICE_TYPE_WIFI;
		else if (g_str_has_prefix(devname, "wmx") == TRUE ||
		    iswimax(devname) == TRUE)
			devtype = CONNMAN_DEVICE_TYPE_WIMAX;
		else if (g_str_has_prefix(devname, "vmnet") == TRUE ||
		    g_str_has_prefix(devname, "vboxnet") == TRUE ||
		    g_str_has_prefix(devname, "bnep") == TRUE ||
		    isbridge(devname) == TRUE ||
		    isbonding(devname) == TRUE)
			devtype = CONNMAN_DEVICE_TYPE_UNKNOWN;
		else if (__connman_udev_has_associated_modem(devname))
			devtype = CONNMAN_DEVICE_TYPE_CELLULAR;
		else
			devtype = CONNMAN_DEVICE_TYPE_ETHERNET;
		break;
	case ARPHRD_NONE:
		break;
	}
	g_free(devname);
	return devtype;
}

/*
 * Create a connman_device given an interface's index.
 */
struct connman_device *connman_inet_create_device(int index)
{
	enum connman_device_mode mode = CONNMAN_DEVICE_MODE_UNKNOWN;
	enum connman_device_type type;
	struct connman_device *device;
	char *devname, *ident = NULL;
	char *addr = NULL, *name = NULL, *node = NULL;

	if (index < 0)
		return NULL;

	devname = connman_inet_ifname(index);
	if (devname == NULL)
		return NULL;
	if (__connman_device_isfiltered(devname) == TRUE) {
		connman_info("Ignoring network interface %s (filtered)",
		    devname);
		g_free(devname);
		return NULL;
	}

	__connman_udev_get_devtype(devname);

	type = __connman_inet_get_device_type(index);

	switch (type) {
	case CONNMAN_DEVICE_TYPE_UNKNOWN:
		connman_info("Ignoring network interface %s (type unknown)",
		    devname);
		g_free(devname);
		return NULL;
	case CONNMAN_DEVICE_TYPE_ETHERNET:
	case CONNMAN_DEVICE_TYPE_WIFI:
	case CONNMAN_DEVICE_TYPE_WIMAX:
	case CONNMAN_DEVICE_TYPE_CELLULAR:
		name = index2ident(index, "");
		addr = index2addr(index);
		break;
	case CONNMAN_DEVICE_TYPE_BLUETOOTH:
	case CONNMAN_DEVICE_TYPE_GPS:
	case CONNMAN_DEVICE_TYPE_VENDOR:
		name = g_strdup(devname);
		break;
	}
	if (name == NULL) {
		connman_error("%s: no memory for network name", __func__);
		device = NULL;
		goto done;
	}

	device = connman_device_create(name, type);
	if (device == NULL)
		goto done;

	switch (type) {
	case CONNMAN_DEVICE_TYPE_UNKNOWN:
	case CONNMAN_DEVICE_TYPE_VENDOR:
	case CONNMAN_DEVICE_TYPE_GPS:
		mode = CONNMAN_DEVICE_MODE_UNKNOWN;
		break;
	case CONNMAN_DEVICE_TYPE_ETHERNET:
		mode = CONNMAN_DEVICE_MODE_TRANSPORT_IP;
		ident = index2ident(index, NULL);
		break;
	case CONNMAN_DEVICE_TYPE_WIFI:
	case CONNMAN_DEVICE_TYPE_WIMAX:
	case CONNMAN_DEVICE_TYPE_CELLULAR:
		mode = CONNMAN_DEVICE_MODE_NETWORK_SINGLE;
		ident = index2ident(index, NULL);
		break;
	case CONNMAN_DEVICE_TYPE_BLUETOOTH:
		mode = CONNMAN_DEVICE_MODE_NETWORK_MULTIPLE;
		break;
	}

	connman_device_set_mode(device, mode);

	connman_device_set_index(device, index);
	connman_device_set_interface(device, devname, node);

	if (ident != NULL) {
		connman_device_set_ident(device, ident);
		g_free(ident);
	}

	connman_device_set_string(device, "Address", addr);
done:
	g_free(devname);
	g_free(node);
	g_free(name);
	g_free(addr);

	return device;
}

/*
 * Set a device's address, netmask, broadcast address, and mtu.
 *
 * NB: We handle partially-formed address record so multiple
 * incomplete ipconfig records can be concatenated.
 */
int connman_inet_set_address(int index, const struct connman_ipaddress *ipaddr)
{
	struct ifreq ifr;
	struct sockaddr_in addr;
	int sk, err;

	if (ipaddr->af != AF_INET) {
		connman_error("%s: no support for af %d", __func__, ipaddr->af);
		return -1;
	}

	sk = getsocket(__func__, PF_INET);
	if (sk < 0)
		return -1;

	err = getifname(sk, index, &ifr, __func__);
	if (err < 0)
		goto done;

	connman_info("Set inet for %s: ipaddr %s bcast %s prefixlen %d mtu %d",
	    ifr.ifr_name, ipaddr->local, ipaddr->broadcast, ipaddr->prefixlen,
	    ipaddr->mtu);

	if (ipaddr->mask & CONNMAN_IPCONFIG_LOCAL) {
		memset(&addr, 0, sizeof(addr));
		addr.sin_family = AF_INET;
		inet_pton(AF_INET, ipaddr->local, &addr.sin_addr);
		memcpy(&ifr.ifr_addr, &addr, sizeof(ifr.ifr_addr));

		err = ioctl(sk, SIOCSIFADDR, &ifr);
		if (err < 0) {
			iferror(__func__, "SIOCSIFADDR", ifr.ifr_name);
			goto done;
		}
	}

	if (ipaddr->mask & CONNMAN_IPCONFIG_PREFIX) {
		memset(&addr, 0, sizeof(addr));
		addr.sin_family = AF_INET;
		/*
		 * For the ">>" operator, the ANSI C standard says that
		 * the result is undefined if the right operand is greater
		 * than equal to the width of the left operand, so we must
		 * handle prefixlen == 32 as a special case.
		 */
		if (ipaddr->prefixlen == 32)
			addr.sin_addr.s_addr = 0xffffffff;
		else
			addr.sin_addr.s_addr =
			    htonl(~(0xfffffffflu >> ipaddr->prefixlen));
		memcpy(&ifr.ifr_netmask, &addr, sizeof(ifr.ifr_netmask));

		err = ioctl(sk, SIOCSIFNETMASK, &ifr);
		if (err < 0) {
			iferror(__func__, "SIOCSIFNETMASK", ifr.ifr_name);
			goto done;
		}
	}

	if (ipaddr->mask & CONNMAN_IPCONFIG_BCAST) {
		memset(&addr, 0, sizeof(addr));
		addr.sin_family = AF_INET;
		inet_pton(AF_INET, ipaddr->broadcast, &addr.sin_addr);
		memcpy(&ifr.ifr_broadaddr, &addr, sizeof(ifr.ifr_broadaddr));

		err = ioctl(sk, SIOCSIFBRDADDR, &ifr);
		if (err < 0) {
			iferror(__func__, "SIOCSIFBRDADDR", ifr.ifr_name);
			goto done;
		}
	}

	if (ipaddr->mask & CONNMAN_IPCONFIG_PEER) {
		memset(&addr, 0, sizeof(addr));
		addr.sin_family = AF_INET;
		inet_pton(AF_INET, ipaddr->peer, &addr.sin_addr);
		memcpy(&ifr.ifr_dstaddr, &addr, sizeof(ifr.ifr_dstaddr));

		err = ioctl(sk, SIOCSIFDSTADDR, &ifr);
		if (err < 0) {
			iferror(__func__, "SIOCSIFDSTADDR", ifr.ifr_name);
			goto done;
		}
		/*
		 * The kernel creates an implicit host route to peer addresses.
		 * Remove that route as it may conflict with with the tunnel's
		 * output IP address.
		 */
		if (connman_inet_del_hostroute(index,
					       addr.sin_addr.s_addr, 0) < 0) {
		        connman_error("%s: unable to remove implicit peer "
				      "host route", __func__);
			goto done;
		}
	}

	if (ipaddr->mask & CONNMAN_IPCONFIG_MTU) {
		ifr.ifr_mtu = ipaddr->mtu;
		err = ioctl(sk, SIOCSIFMTU, &ifr);
		if (err < 0)
			iferror(__func__, "SIOCSIFMTU", ifr.ifr_name);
	}
done:
	close(sk);
	return err;
}

/*
 * Clear all non-static globally scoped IPv6 addresses from the interface
 */
static int connman_inet_clear_ipv6_address(int index)
{
	int sk6;
	FILE *inet6_file;
	char addr6p[8][5];
	char addr6[40], devname[21];
	int if_idx, prefix_len, scope, flags;
	int ret;
	int found = 0;
	/*
	 * TODO(pstew): This is a local definition of in6_ifreq.  This
	 * is because the only file that defines it, linux/ipv6.h,
	 * does not expect to be included by user code, and causes
	 * compile errors
	 */
	struct in6_ifreq {
		struct in6_addr ifr6_addr;
		uint32_t ifr6_prefixlen;
		int ifr6_ifindex; 
	} ifr6;
      
	sk6 =  getsocket(__func__, PF_INET6);
	if (sk6 < 0)
		return -1;
	
	ifr6.ifr6_ifindex = index;
	
	/*
	 * This is how ifconfig.c queries IPv6 addresses.
	 * We could use netlink, but then this code will get much bigger.
	 */
	if ((inet6_file = fopen("/proc/net/if_inet6", "r")) == NULL) {
		close(sk6);
		return -1;
	}

	do {
		ret = fscanf(inet6_file, "%4s%4s%4s%4s%4s%4s%4s%4s "
			     "%x %x %x %x %20s\n",
			     addr6p[0], addr6p[1], addr6p[2], addr6p[3],
			     addr6p[4], addr6p[5], addr6p[6], addr6p[7],
			     &if_idx, &prefix_len, &scope, &flags,
			     devname);
		if (ret != 13 || if_idx != ifr6.ifr6_ifindex)
			continue;

		/*
		 * If this address is not globally scoped, or has any 
		 * funny flags on it (for example "permanent address")
		 * skip it.  We do, however accept addresses marked as
		 * "temporary".
		 */
		if (scope != RT_SCOPE_UNIVERSE ||
		    (flags & ~IFA_F_TEMPORARY) != 0)
			continue;

		snprintf(addr6, sizeof(addr6), "%s:%s:%s:%s:%s:%s:%s:%s",
			addr6p[0], addr6p[1], addr6p[2], addr6p[3],
			addr6p[4], addr6p[5], addr6p[6], addr6p[7]);

		if (inet_pton(AF_INET6, addr6, &ifr6.ifr6_addr) < 0) {
			connman_error("%s: inet_pton %s %s", __func__,
				      addr6, strerror(errno));
			continue;
		}
			
		ifr6.ifr6_prefixlen = prefix_len;
		if (ioctl(sk6, SIOCDIFADDR, &ifr6) < 0) {
			connman_error("%s: SIOCDIFADDR %s/%d %s", __func__,
				      addr6, prefix_len, strerror(errno));
			continue;
		}

		connman_info("Clear inet6 address %s/%d\n", addr6, prefix_len);
		found++;
	} while (ret != EOF);

	fclose(inet6_file);
	close(sk6);
	
	return found;
}

/*
 * Clear IPv4 addresss from the interface
 */
int connman_inet_clear_ipv4_address(int index)
{
	struct ifreq ifr;
	struct sockaddr_in addr;
	int sk, err;

	sk = getsocket(__func__, PF_INET);
	if (sk < 0)
		return -1;

	err = getifname(sk, index, &ifr, __func__);
	if (err < 0)
		goto done;

	connman_info("Clear inet for %s", ifr.ifr_name);

	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = INADDR_ANY;
	memcpy(&ifr.ifr_addr, &addr, sizeof(ifr.ifr_addr));

	//err = ioctl(sk, SIOCDIFADDR, &ifr);
	err = ioctl(sk, SIOCSIFADDR, &ifr);
	if (err < 0) {
		if (errno != EADDRNOTAVAIL)
			iferror(__func__, "SIOCSIFADDR", ifr.ifr_name);
		else
			err = 0;		/* NB: ignore error */
	}


done:
	close(sk);
	return err;
}

/*
 * Clear all addrconf-created IPv6 routes from the interface
 */
static void connman_inet_clear_ipv6_route(int index)
{
	struct connman_rtnl_reader *rt;
	struct in6_addr dst, src, gateway;
	unsigned char src_len, dst_len;
	struct in6_rtmsg msg;
	int sk6;
	unsigned int flags;

	sk6 = getsocket(__func__, PF_INET6);
	if (sk6 < 0)
		connman_error("%s: socket: %s", __func__, strerror(errno));

	rt = connman_rtnl_open_route(AF_INET6, NLM_F_DUMP, NULL, index);
	if (rt == NULL)
		goto done;

	while (connman_rtnl_read_route(rt, NULL, &dst_len, &src_len,
				       &dst, &src, &gateway, &flags)) {
		if (IN6_IS_ADDR_LINKLOCAL(&dst) || IN6_IS_ADDR_MULTICAST(&dst))
			continue;
		memset(&msg, 0, sizeof(msg));
		msg.rtmsg_type = RTMSG_DELROUTE;
		msg.rtmsg_dst = dst;
		msg.rtmsg_dst_len = dst_len;
		msg.rtmsg_src = src;
		msg.rtmsg_src_len = src_len;
		msg.rtmsg_gateway = gateway;
		msg.rtmsg_ifindex = index;
		/* NB: Fail silently since this operation is racy */
		ioctl(sk6, SIOCDELRT, &msg);
	}

	connman_rtnl_close(rt);

done:
	close(sk6);
}

/*
 * Clear any address associated with a device.
 */
void connman_inet_clear_address(int index)
{
	connman_inet_clear_ipv4_address(index);
}


static void connman_inet_set_ipv6_disabled(int index, int value)
{
	struct ifreq ifr;
	int sk, err;
	char *filename;
	FILE *autoconf_file = NULL;

	sk = getsocket(__func__, PF_INET);
	if (sk < 0)
		return;

	err = getifname(sk, index, &ifr, __func__);
	close(sk);
	if (err < 0)
		return;

	filename = g_strdup_printf("/proc/sys/net/ipv6/conf/%s/disable_ipv6",
				   ifr.ifr_name);
	autoconf_file = fopen(filename, "w");
	g_free(filename);

	if (autoconf_file == NULL) {
		connman_error("%s: Open autoconf for %s: %s",
			      __func__, ifr.ifr_name, strerror(errno));
		return;
	}
	fprintf(autoconf_file, "%d", value);
	fclose(autoconf_file);
}

void connman_inet_enable_ipv6(int index)
{
	/*
	 * Kicking this file ensures that IPv6 address configuration starts
	 * immediately.
	 */
	connman_inet_set_ipv6_disabled(index, 0);
}

void connman_inet_disable_ipv6(int index)
{
	/*
	 * Since we never "down" the interface, IPv6 addresses assigned
	 * to this interface will persist unless we remove them explicitly.
	 * Not doing so will confuse client applications that use the
	 * existence of a globally scoped IPv6 address to determine whether
	 * to connect to IPv6 vs IPv4 addresses.
	 */
	connman_inet_set_ipv6_disabled(index, 1);
	connman_inet_clear_ipv6_address(index);
	connman_inet_clear_ipv6_route(index);
}

/*
 * Set IPv6 privacy on a network interface
 */
void connman_inet_set_ipv6_privacy(const char *ifname)
{
	char *file = g_strdup_printf("/proc/sys/net/ipv6/conf/%s/use_tempaddr",
				     ifname);
	FILE *privacy_file = fopen(file, "w");

	g_free(file);
	if (privacy_file == NULL) {
		connman_error("%s: Open use_tempaddr for %s: %s",
			      __func__, ifname, strerror(errno));
		return;
	}
	if (fwrite("2", 1, 1, privacy_file) != 1) {
		connman_error("%s: Write use_tempaddr for %s: %s",
			      __func__, ifname, strerror(errno));
	}
	fclose(privacy_file);
}

static void setup_rt(struct rtentry *rt, int flags, in_addr_t *dst,
    in_addr_t *gw, in_addr_t *mask, short metric)
{
	struct sockaddr_in addr;

	memset(rt, 0, sizeof(*rt));
	rt->rt_flags = flags;

	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = (dst != NULL ? *dst : INADDR_ANY);
	memcpy(&rt->rt_dst, &addr, sizeof(rt->rt_dst));

	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = (gw != NULL ? *gw : INADDR_ANY);
	memcpy(&rt->rt_gateway, &addr, sizeof(rt->rt_gateway));

	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = (mask != NULL ? *mask : INADDR_ANY);
	memcpy(&rt->rt_genmask, &addr, sizeof(rt->rt_genmask));

	rt->rt_metric = metric;
}

/*
 * Route packets through the specified device.
 * Both host and (default) gateway routes are installed.
 */
int connman_inet_set_gateway(int index, in_addr_t gateway, short metric)
{
	struct ifreq ifr;
	struct rtentry rt;
	int sk, err;
	char gwbuf[16];

	sk = getsocket(__func__, PF_INET);
	if (sk < 0)
		return -1;

	err = getifname(sk, index, &ifr, __func__);
	if (err < 0)
		goto done;

	connman_info("Set default route for %s: gw %s metric %d",
	    ifr.ifr_name,
	    inet_ntop(AF_INET, &gateway, gwbuf, sizeof(gwbuf)),
	    metric);

	/*
	 * Linux requires an existing route for any gateway (and it
	 * must use the same interface).  Satisfy this by always
	 * creating a host route.
	 */
	setup_rt(&rt, RTF_UP | RTF_HOST, &gateway, NULL, NULL, metric);
	rt.rt_dev = ifr.ifr_name;
	err = ioctl(sk, SIOCADDRT, &rt);
	if (err < 0 && errno != EEXIST) {
		iferror(__func__, "SIOCADDRT (host)", ifr.ifr_name);
		goto done;
	}

	setup_rt(&rt, RTF_UP | RTF_GATEWAY, NULL, &gateway, NULL, metric);
	rt.rt_dev = ifr.ifr_name;
	err = ioctl(sk, SIOCADDRT, &rt);
	if (err < 0) {
		if (errno != EEXIST)
			iferror(__func__, "SIOCADDRT (gw)", ifr.ifr_name);
		else
			err = 0;
	}

	/* Delete the unnecessary host route we created above. */
	setup_rt(&rt, RTF_UP | RTF_HOST, &gateway, NULL, NULL, metric);
	rt.rt_dev = ifr.ifr_name;
	err = ioctl(sk, SIOCDELRT, &rt);
	if (err < 0)
		iferror(__func__, "SIOCDELRT (host)", ifr.ifr_name);

done:
	close(sk);
	return err;
}

/*
 * Clear routing state installed by connman_inet_set_gateway.
 */
int connman_inet_del_gateway(int index, in_addr_t gateway, short metric)
{
	struct ifreq ifr;
	struct rtentry rt;
	int sk, err;

	sk = getsocket(__func__, PF_INET);
	if (sk < 0)
		return -1;

	err = getifname(sk, index, &ifr, __func__);
	if (err < 0)
		goto done;

	connman_info("Delete default route through %s metric %d",
	    ifr.ifr_name, metric);

	setup_rt(&rt, RTF_UP | RTF_GATEWAY, NULL, &gateway, NULL, metric);
	rt.rt_dev = ifr.ifr_name;
	err = ioctl(sk, SIOCDELRT, &rt);
	if (err < 0)
		iferror(__func__, "SIOCDELRT (gw)", ifr.ifr_name);
done:
	close(sk);
	return err;
}

int connman_inet_add_route(int index, in_addr_t ipaddr, in_addr_t netmask,
    in_addr_t gateway)
{
	struct ifreq ifr;
	struct rtentry rt;
	int sk, err;
	char addrbuf[16], maskbuf[16], gwbuf[16];

	sk = getsocket(__func__, PF_INET);
	if (sk < 0)
		return -1;

	err = getifname(sk, index, &ifr, __func__);
	if (err < 0)
		goto done;

	connman_info("Add network route through %s: ip %s mask %s gw %s",
	    ifr.ifr_name,
	    inet_ntop(AF_INET, &ipaddr, addrbuf, sizeof(addrbuf)),
	    inet_ntop(AF_INET, &netmask, maskbuf, sizeof(maskbuf)),
	    inet_ntop(AF_INET, &gateway, gwbuf, sizeof(gwbuf)));

	setup_rt(&rt, RTF_UP | RTF_GATEWAY, &ipaddr, &gateway, &netmask, 0);
	rt.rt_dev = ifr.ifr_name;
	err = ioctl(sk, SIOCADDRT, &rt);
	if (err < 0) {
		if (errno != EEXIST)
			iferror(__func__, "SIOCADDRT (network)", ifr.ifr_name);
		else
			err = 0;
	}
done:
	close(sk);
	return err;
}

int connman_inet_add_hostroute(int index, in_addr_t ipaddr, in_addr_t gateway)
{
	struct ifreq ifr;
	struct rtentry rt;
	int sk, err;
	char addrbuf[16], gwbuf[16];
	int flags;

	sk = getsocket(__func__, PF_INET);
	if (sk < 0)
		return -1;

	err = getifname(sk, index, &ifr, __func__);
	if (err < 0)
		goto done;

	connman_info("Add host route through %s: ip %s gw %s", ifr.ifr_name,
	    inet_ntop(AF_INET, &ipaddr, addrbuf, sizeof(addrbuf)),
	    inet_ntop(AF_INET, &gateway, gwbuf, sizeof(gwbuf)));

	flags = RTF_UP | RTF_HOST;
	if (gateway != INADDR_ANY)
	        flags |= RTF_GATEWAY;
	setup_rt(&rt, flags, &ipaddr, &gateway, NULL, 0);
	rt.rt_dev = ifr.ifr_name;
	err = ioctl(sk, SIOCADDRT, &rt);
	if (err < 0) {
		if (errno != EEXIST)
			iferror(__func__, "SIOCADDRT (host)", ifr.ifr_name);
		else
			err = 0;
	}
done:
	close(sk);
	return err;
}

/* TODO(kmixter) support IPv6 */
connman_bool_t connman_inet_get_route(in_addr_t dest, uint32_t *index,
				      in_addr_t *gateway, in_addr_t *src)
{
	connman_bool_t ret;
	struct connman_rtnl_reader *rt =
	    connman_rtnl_open_route(AF_INET, 0, &dest, -1);

	if (rt == NULL)
		return FALSE;

	ret = connman_rtnl_read_route(rt, index, NULL, NULL, NULL,
				      src, gateway, NULL);
	connman_rtnl_close(rt);

	return ret;
}

int connman_inet_del_hostroute(int index, in_addr_t ipaddr, in_addr_t gateway)
{
	struct ifreq ifr;
	struct rtentry rt;
	int sk, err;
	char addrbuf[16], gwbuf[16];
	int flags;

	sk = getsocket(__func__, PF_INET);
	if (sk < 0)
		return -1;

	err = getifname(sk, index, &ifr, __func__);
	if (err < 0)
		goto done;

	connman_info("Del host route through %s: ip %s gw %s", ifr.ifr_name,
	    inet_ntop(AF_INET, &ipaddr, addrbuf, sizeof(addrbuf)),
	    inet_ntop(AF_INET, &gateway, gwbuf, sizeof(gwbuf)));

	flags = RTF_UP | RTF_HOST;
	if (gateway != INADDR_ANY)
	        flags |= RTF_GATEWAY;
	setup_rt(&rt, flags, &ipaddr, &gateway, NULL, 0);
	rt.rt_dev = ifr.ifr_name;
	err = ioctl(sk, SIOCDELRT, &rt);
	if (err < 0) {
		if (errno != EEXIST)
			iferror(__func__, "SIOCDELRT (host)", ifr.ifr_name);
		else
			err = 0;
	}
done:
	close(sk);
	return err;
}
